package com.xiaoman.international;



import com.xiaoman.international.annotation.LanguageConvert;
import com.xiaoman.international.pojo.BatchConvertDto;
import com.xiaoman.international.util.ReflectUtil;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import sun.reflect.generics.reflectiveObjects.TypeVariableImpl;
import java.lang.reflect.*;
import java.util.*;

@Component
public class CommonInternationHandle {
    private static International international;
    public CommonInternationHandle(International international) {
        CommonInternationHandle.international = international;
    }

    private static synchronized void setInternational(Object data, Locale locale, String internationType) throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException {
        Map<Object, Map<LanguageConvert, Object>> map = new HashMap<>();
        handleInternational(data, map);
        if (map.isEmpty()) {
            return;
        }
        Map<String, BatchConvertDto> internationalTypeMap = new HashMap<>();
        for (Map<LanguageConvert, Object> objectMap : map.values()) {
            for (Map.Entry<LanguageConvert, Object> languageConvertObjectEntry : objectMap.entrySet()) {
                String type = StringUtils.isNotBlank(internationType) && !internationType.equalsIgnoreCase("unkonwn") ? internationType : languageConvertObjectEntry.getKey().value();
                if (StringUtils.isBlank(type) || type.equalsIgnoreCase("unkonwn"))
                    continue;
                internationalTypeMap.putIfAbsent(type, new BatchConvertDto());
                BatchConvertDto batchConvertDto = internationalTypeMap.get(type);
                if (batchConvertDto.getKeys() == null) batchConvertDto.setKeys(new HashSet<>());
                batchConvertDto.getKeys().add(String.valueOf(languageConvertObjectEntry.getValue()));
            }
        }
        Map<Object, Map<String, String>> batchCacheConvertMap = new HashMap<>();
        internationalTypeMap.forEach((key, convertDto) -> {
            convertDto.setInternationalType(key);
            convertDto.setLocale(locale);
            batchCacheConvertMap.put(key, international.getInternationalMap(convertDto));
        });
        for (Map.Entry<Object, Map<LanguageConvert, Object>> mapEntry : map.entrySet()) {
            Object key = mapEntry.getKey();
            Map<LanguageConvert, Object> convertObjectMap = mapEntry.getValue();
//            Optional<Map.Entry<LanguageConvert, Object>> convertObjectEntry = convertObjectMap.entrySet().stream().findFirst();
            for (Map.Entry<LanguageConvert, Object> convertObjectEntry : convertObjectMap.entrySet()) {
//                if (!convertObjectEntry.isPresent()) continue;
                LanguageConvert languageConvert = convertObjectEntry.getKey();
                String value = String.valueOf(convertObjectEntry.getValue());
                String type = StringUtils.isNotBlank(internationType) && !internationType.equalsIgnoreCase("unkonwn") ? internationType : languageConvert.value();
                Map<String, String> internationValueMap = batchCacheConvertMap.get(type);
                if (null == internationValueMap) continue;
                String s = internationValueMap.get(value);
                if (StringUtils.isNotBlank(s))
                    ReflectUtil.setFieldValue(key, languageConvert.linkNameField(), s);

            }
        }
    }
    public static void baseSetInternational(Object data, Locale locale, String internationalType) throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException {
        if (Objects.isNull(data))
            return;
        Class<?> clazz = data.getClass();
        if (Collection.class.isAssignableFrom(clazz)) {
            Collection<?> list = (Collection<?>) data;
            for (Object object : list) {
                setInternational(object, locale, internationalType);
            }
        } else {
            setInternational(data, locale, internationalType);
        }
    }
    private static void handleInternational(Object data, Map<Object, Map<LanguageConvert, Object>> map) throws ClassNotFoundException, IllegalAccessException {
        if (null == data) return;
        Class<?> clazz = data.getClass();
        Field[] fields = getAllDeclareFields(clazz);
        for (Field field : fields) {
            Class<?> fieldClass = field.getType();
            if (Collection.class.isAssignableFrom(field.getType())) {
                Type genericType = field.getGenericType();
                if(genericType instanceof ParameterizedType){
                    ParameterizedType pt = (ParameterizedType) genericType;
                    if (!pt.getActualTypeArguments()[0].getClass().isAssignableFrom(Class.class)) {
                        if (pt.getActualTypeArguments()[0].getClass().isAssignableFrom(TypeVariableImpl.class)) {
                            TypeVariableImpl t = (TypeVariableImpl) pt.getActualTypeArguments()[0];
                            fieldClass =  Class.forName(t.getBounds()[0].getTypeName());
                        } else
                            continue;
                    } else {
                        fieldClass = (Class<?>)pt.getActualTypeArguments()[0];
                    }
                }
                List<?> fieldList = (List<?>) ReflectUtil.getFieldValue(data, field);
                if (null == fieldList) continue;
                for (Object fieldValue : fieldList) {
                    addLanguageConvertMap(map, fieldClass, fieldValue, field, data);
                }
            } else {
                Object fieldValue = ReflectUtil.getFieldValue(data, field);
//                if (fieldValue == null) continue;
                addLanguageConvertMap(map, fieldClass, fieldValue, field, data);
            }
        }
    }

    private static void addLanguageConvertMap(Map<Object, Map<LanguageConvert, Object>> map, Class<?> fieldClass, Object fieldValue, Field field, Object data) throws ClassNotFoundException, IllegalAccessException {
        if (fieldClass.getClassLoader() != null) handleInternational(fieldValue, map);
        LanguageConvert languageConvert = field.getDeclaredAnnotation(LanguageConvert.class);
        if (null == languageConvert) return;
        Map<LanguageConvert, Object> subMap = map.get(data) == null ? new HashMap<>() : map.get(data);
        map.putIfAbsent(data, subMap);
        Object finalFieldValue = fieldValue;
        if (StringUtils.isBlank(languageConvert.linkNameField()))
            updateAnnotationMembers(languageConvert, "linkNameField", field.getName());
        if (StringUtils.isNotBlank(languageConvert.collegatoField())) {
            try {
                finalFieldValue = ReflectUtil.getFieldValue(data, data.getClass().getDeclaredField(languageConvert.collegatoField()));
            } catch (NoSuchFieldException e) {
            }
        }
        subMap.put(languageConvert, finalFieldValue);
    }

    private static Field[] getAllDeclareFields(Class<?> clazz) {
        Field[] fields = clazz.getDeclaredFields();
        while (!((clazz = clazz.getSuperclass()).equals(Object.class)))
            fields = ArrayUtils.addAll(fields, clazz.getDeclaredFields());
        return fields;
    }

    @SuppressWarnings("unchecked")
    private static void updateAnnotationMembers(LanguageConvert languageConvert, String memberNames, Object value) {
        try {
            InvocationHandler h = Proxy.getInvocationHandler(languageConvert);
            Field hField = h.getClass().getDeclaredField("memberValues");
            hField.setAccessible(true);
            Map memberValues = (Map) hField.get(h);
            memberValues.put(memberNames, value);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}
